package com.github.chirspan.xaas.gateway.filter;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.github.chirspan.xaas.gateway.component.FeignClientFactory;
import com.github.chirspan.xaas.gateway.service.OAuthClient;
import com.github.chirspan.xaas.core.config.IgnoreUrlsProperties;
import com.github.chirspan.xaas.core.constant.SecurityConstants;
import com.github.chirspan.xaas.core.rest.RestResult;
import com.github.chirspan.xaas.core.rest.RestResultStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

/**
 * *******description*******
 * 权限校验拦截器
 * *************************
 *
 * @author chenpan
 * @date 2020/5/25 10:59
 */
@Component
public class TokenGatewayFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    IgnoreUrlsProperties ignoreUrlsProperties;

    @Autowired
    FeignClientFactory feignClientFactory;

    @Override
    public int getOrder() {
        return 10;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (exchange.getAttribute(IgnoreGlobalFilterFactor.ATTRIBUTE_IGNORE_GLOBAL_FILTER) != null) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        //getOrder 调整了优先级在重写路径之后
        //String newPath = getRealPath(exchange);
        String newPath = request.getURI().getPath();
        //不拦截的请求
        if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(),
                SecurityConstants.OAUTH_TOKEN_URL,
                SecurityConstants.CHECK_TOKEN_URL,
                SecurityConstants.AUTH_BASE_URL,
                SecurityConstants.API_DOC_URL)) {
            return chain.filter(exchange);
        }
        //忽略的请求
        if (MapUtil.isNotEmpty(ignoreUrlsProperties.getUrls())) {
            for (Map.Entry<String, String> m : ignoreUrlsProperties.getUrls().entrySet()) {
                boolean ignore = antPathMatcher.match(m.getKey(), newPath)
                        && (m.getValue().contains(request.getMethodValue()) || m.getValue().contains("*"));
                if (ignore) {
                    return chain.filter(exchange);
                }
            }
        }
        String tokenId = null;
        //从header取
        String token = request.getHeaders().getFirst(SecurityConstants.AUTHORIZATION);
        if (token != null && token.contains(SecurityConstants.BEARER)) {
            tokenId = token.substring(SecurityConstants.BEARER.length() + 1);
        }
        if (StrUtil.isEmpty(tokenId)) {
            //尝试从param取
            tokenId = request.getQueryParams().getFirst(SecurityConstants.TOKEN_KEY);
        }
        if (StrUtil.isEmpty(tokenId)) {
            RestResult result = new RestResult(RestResultStatus.LOST_TOKEN);
            return responseFailRs(exchange, result);
        }
        //校验token
        try {
            OAuthClient oAuthClient = feignClientFactory.oAuthClient(tokenId);
            RestResult checkTokenResult = oAuthClient.checkToken(tokenId);
            if (!checkTokenResult.isSuccess()) {
                return responseFailRs(exchange, checkTokenResult);
            }
            //校验权限
            if (!oAuthClient.checkPermission(tokenId, newPath, request.getMethod().name())) {
                RestResult result = new RestResult(RestResultStatus.UNAUTH_ERROR, newPath);
                return responseFailRs(exchange, result);
            }

            // 增加自定义用户头信息
            Map<String, ?> jwtMap = (LinkedHashMap<String, ?>) checkTokenResult.getData();
            exchange.getRequest().mutate().header(SecurityConstants.USER_HEADER, (String) jwtMap.get(SecurityConstants.JWT_USER_NAME))
                    .header(SecurityConstants.USER_ID_HEADER, (String) jwtMap.get(SecurityConstants.JWT_USER_ID))
                    .header(SecurityConstants.ROLE_HEADER, CollectionUtil.join((ArrayList) jwtMap.get(SecurityConstants.JWT_USER_AUTHORITIES), ","))
                    .header(SecurityConstants.CLIENT_HEADER, (String) jwtMap.get(SecurityConstants.JWT_CLIENT))
                    .build();

        } catch (Exception e) {
            e.printStackTrace();
            RestResult result = new RestResult(RestResultStatus.CHECK_TOKEN_ERROR, e.getMessage());
            return responseFailRs(exchange, result);
        }

        return chain.filter(exchange);

    }

    private Mono<Void> responseFailRs(ServerWebExchange exchange, RestResult result) {
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        serverHttpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
        if (String.valueOf(RestResultStatus.UNAUTH_ERROR.getCode()).equals(result.getCode())) {
            serverHttpResponse.setStatusCode(HttpStatus.FORBIDDEN);
        }
        serverHttpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        byte[] bytes = JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
        return serverHttpResponse.writeWith(Flux.just(buffer));
    }

    private String getRealPath(ServerWebExchange exchange) {
        addOriginalRequestUrl(exchange, exchange.getRequest().getURI());
        String newPath = "/" + (String) Arrays.stream(
                org.springframework.util.StringUtils.tokenizeToStringArray(exchange.getRequest().getURI().getRawPath(), "/"))
                .skip(1L).collect(Collectors.joining("/"));
        newPath = newPath + (newPath.length() > 1 && exchange.getRequest().getURI().getRawPath().endsWith("/") ? "/" : "");
        return newPath;
    }

}
